home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Collections: Camelot
/
Camelot 030 (1988-11)(Swedish User Group of Amiga)(SE)(PD)[WB].zip
/
Camelot 030 (1988-11)(Swedish User Group of Amiga)(SE)(PD)[WB].adf
/
PathDev
/
DEVICE.C
< prev
next >
Wrap
C/C++ Source or Header
|
1988-10-25
|
22KB
|
804 lines
/*
* DOSDEVICE.C V1.10 2 November 1987
*
* EXAMPLE DOS DEVICE DRIVER FOR AZTEC.C PUBLIC DOMAIN.
*
* By Matthew Dillon.
*
* Debugging routines are disabled by simply attempting to open the
* file "debugoff", turned on again with "debugon". No prefix may be
* attached to these names (you must be CD'd to TEST:).
*
* See Documentation for a detailed discussion.
*
* BUGS:
* Currently the only known bug is with the implementation of the
* RAM disk itself. Specifically, if filehandle A is at the end of
* the file, and somebody appends to the file with another filehandle,
* B, filehandle A will get confused as to it's current position in
* the file.
*
* I am probably not updating all the right timestamps. This is
* easy to fix... All you have to do is fool with the floppy and
* see which datestamps get updated for certain operations.
*/
#include "dos.h"
/*
* Since this code might be called several times in a row without being
* unloaded, you CANNOT ASSUME GLOBALS HAVE BEEN ZERO'D!! This also goes
* for any global/static assignments that might be changed by running the
* code.
*/
PORT *DosPort; /* Our DOS port... this is slick... */
PROC *DosProc; /* Our Process */
DEVNODE *DosNode; /* Our DOS node.. created by DOS for us */
DEVLIST *DevList; /* Device List structure for our volume node */
void *SysBase; /* EXEC library base */
DOSLIB *DOSBase; /* DOS library base for debug process */
RAMFILE RFRoot; /* Directory/File structure (root node) */
LIST FHBase; /* Open Files */
LIST LCBase; /* Open Locks */
long TotalBytes; /* total bytes of data in filesystem */
/* DEBUGGING */
PORT *Dbport; /* owned by the debug process */
PORT *Dback; /* owned by the DOS device driver */
MSG DummyMsg; /* Dummy message that debug proc can use */
RAMFILE xpath; /* This is used in case of off device path */
char *buf1; /* This holds the translated path names */
char *volname; /* This is my volume name */
RAMFILE * checkoutpath();
RAMFILE * vsearchpath();
void *DeviceProc();
/*
* Don't call the entry point main(). This way, if you make a mistake
* with the compile options you'll get a link error.
*/
void
noname()
{
register PACKET *packet;
register short error;
register ubyte *ptr;
MSG *msg;
ubyte notdone;
ubyte buf[256];
void *tmp;
/*
* Initialize all global variables. SysBase MUST be initialized before
* we can make Exec calls. AbsExecBase is a library symbol
* referencing absolute memory location 4. The DOS library is openned
* for the debug process only.
*/
Dbport = Dback = NULL;
TotalBytes = 0;
SysBase = AbsExecBase;
DOSBase = OpenLibrary("dos.library",0);
DosProc = FindTask(NULL);
DosPort = (PORT *)AllocMem(sizeof(PORT), MEMF_CLEAR | MEMF_PUBLIC);
DosPort->mp_Node.ln_Type = NT_MSGPORT;
DosPort->mp_Node.ln_Name = "Dos Port";
DosPort->mp_Flags = PA_SIGNAL;
DosPort->mp_SigBit = AllocSignal(-1);
DosPort->mp_SigTask = FindTask(NULL);
NewList(&DosPort->mp_MsgList);
buf1 = AllocMem(512, MEMF_PUBLIC);
{
WaitPort(&DosProc->pr_MsgPort); /* Get Startup Packet */
msg = GetMsg(&DosProc->pr_MsgPort);
packet = (PACKET *)msg->mn_Node.ln_Name;
/*
* Loading DosNode->dn_Task causes DOS *NOT* to startup a new
* instance of the device driver for every reference. E.G. if
* you were writing a CON device you would want this field to
* be NULL.
*/
if (DOSBase) {
DOSINFO *di = BTOC(((ROOTNODE *)DOSBase->dl_Root)->rn_Info);
register DEVLIST *dl = dosalloc(sizeof(DEVLIST));
DosNode = BTOC(PArg3);
/*
* Create Volume node and add to the device list. This will
* cause the WORKBENCH to recognize us as a disk. If we don't
* create a Volume node, Wb will not recognize us. However,
* we are a RAM: disk, Volume node or not.
*/
volname = AllocMem(32,MEMF_PUBLIC);
volname[0] = 11;
strcpy(volname+1,"Path Server");
DevList = dl;
dl->dl_Type = DLT_VOLUME;
dl->dl_Task = DosPort;
dl->dl_DiskType = ID_DOS_DISK;
dl->dl_Name = CTOB(volname); /* DosNode->dn_Name*/;
dl->dl_Next = di->di_DevInfo;
di->di_DevInfo = (long)CTOB(dl);
/*
* Set dn_Task field which tells DOS not to startup a new
* process on every reference.
*/
DosNode->dn_Task = DosPort;
PRes1 = DOS_TRUE;
PRes2 = 0;
} else { /* couldn't open dos.library */
PRes1 = DOS_FALSE;
returnpacket(packet);
return; /* exit process */
}
returnpacket(packet);
}
/*
* Initialize debugging code
*/
/*DB*/ dbinit();
/* Initialize RAM disk */
{
ubyte *ptr = BTOC(DosNode->dn_Name);
short len = *ptr;
NewList(&FHBase); /* more globals */
NewList(&LCBase);
bzero(&RFRoot,sizeof(RFRoot));
RFRoot.type = FILE_DIR; /* root directory */
DateStamp(&RFRoot.date); /* datestamp */
NewList(&RFRoot.list); /* sub dirs */
RFRoot.name = AllocMem(len+1, MEMF_PUBLIC); /* Root NAME */
bmov(ptr+1,RFRoot.name,len);
RFRoot.name[len] = 0;
/*DB*/ dbprintf("ROOT NAME: %ld '%s'\n", len, RFRoot.name);
}
/*
* Here begins the endless loop, waiting for requests over our
* message port and executing them. Since requests are sent over
* our message port, this precludes being able to call DOS functions
* ourselves (that is why the debugging routines are a separate process)
*/
WaitPort(&DosProc->pr_MsgPort); /* Get Startup Packet */
msg = GetMsg(&DosProc->pr_MsgPort);
notdone = 1;
goto entry;
top:
for (notdone = 1; notdone;) {
WaitPort(DosPort);
while (msg = GetMsg(DosPort)) {
entry:
packet = (PACKET *)msg->mn_Node.ln_Name;
PRes1 = DOS_TRUE;
PRes2 = 0;
error = 0;
/*DB*/ dbprintf("Packet: %3ld %08lx %08lx %08lx %10s ",
/*DB*/ PType, PArg1, PArg2, PArg3, typetostr(PType) );
switch(PType) {
case ACTION_DIE: /* attempt to die? */
notdone = 0; /* try to die */
break;
case ACTION_OPENRW: /* FileHandle,Lock,Name Bool */
case ACTION_OPENOLD: /* FileHandle,Lock,Name Bool */
case ACTION_OPENNEW: /* FileHandle,Lock,Name Bool */
{
register RAMFILE *ramfile;
RAMFILE *parentdir = getlockfile(PArg2);
char *ptr;
btos(PArg3,buf);
/*DB*/ dbprintf("'%s' ", buf);
if (ramfile = searchpath(&parentdir,buf,&ptr)) {
if (ramfile == &xpath) {
FH *p;
if (p = Open(buf1,PType)) {
bmov(BTOC(p),BTOC(PArg1),sizeof(FH));
FreeMem(BTOC(p),sizeof(FH));
}
else
error = IoErr();
goto openbreak;
}
if (ramfile->type == FILE_DIR) {
error = ERROR_OBJECT_WRONG_TYPE;
goto openbreak;
}
if (ramfile->locks < 0) {
error = ERROR_OBJECT_IN_USE;
goto openbreak;
}
if (PType == ACTION_OPENOLD) {
++ramfile->locks;
} else {
if (ramfile->locks > 0) {
error = ERROR_OBJECT_IN_USE;
} else {
if (PType == ACTION_OPENNEW) {
freedata(ramfile);
ramfile->protection = 0;
}
--ramfile->locks;
}
}
} else {
if (!parentdir) {
error = ERROR_INVALID_COMPONENT_NAME;
goto openbreak;
}
if (PType == ACTION_OPENNEW) {
ramfile = createramfile(parentdir,FILE_FILE,ptr);
--ramfile->locks;
} else {
error = ERROR_OBJECT_NOT_FOUND;
}
}
if (!error) {
register MYFH *mfh = AllocMem(sizeof(MYFH), MEMF_PUBLIC|MEMF_CLEAR);
((FH *)BTOC(PArg1))->fh_Arg1 = (long)mfh;
mfh->file = ramfile;
mfh->fentry = GetHead(&ramfile->list);
AddHead(&FHBase,mfh);
}
}
openbreak:
if (!GetHead(&FHBase) && !GetHead(&LCBase))
notdone = 0;
break;
case ACTION_READ: /* FHArg1,CPTRBuffer,Length ActLength */
{
register MYFH *mfh = (MYFH *)PArg1;
register FENTRY *fen = mfh->fentry;
register ubyte *ptr = (ubyte *)PArg2;
register long left = PArg3;
register long scr;
while (left && fen) {
scr = fen->bytes - mfh->offset;
if (left < scr) {
bmov(fen->buf + mfh->offset, ptr, left);
mfh->offset += left;
left = 0;
} else {
bmov(fen->buf + mfh->offset, ptr, scr);
left -= scr;
ptr += scr;
mfh->base += fen->bytes;
mfh->offset = 0;
fen = NextNode(fen);
}
}
mfh->fentry = fen;
PRes1 = PArg3 - left;
}
break;
case ACTION_WRITE: /* FHArg1,CPTRBuffer,Length ActLength */
{
register MYFH *mfh = (MYFH *)PArg1;
register FENTRY *fen = (FENTRY *)mfh->fentry;
ubyte *ptr = (ubyte *)PArg2;
long left = PArg3;
long scr;
/*
* Doesn't work right if multiple readers/appenders.
*/
while (left) {
if (fen) {
/*DB*/ dbprintf("FEN: %ld left: %ld\n",fen->bytes,left);
scr = fen->bytes - mfh->offset;
if (left < scr) {
if (fen->bytes < mfh->offset + left) {
/*DB*/ dbprintf("PANIC! AWR0\n");
}
else
bmov(ptr, fen->buf + mfh->offset, left);
mfh->offset += left;
left = 0;
} else {
if (fen->bytes < mfh->offset + scr) {
/*DB*/ dbprintf("PANIC! AWR1\n");
}
else
bmov(ptr, fen->buf + mfh->offset, scr);
ptr += scr;
left -= scr;
mfh->base += fen->bytes;
mfh->offset = 0;
fen = NextNode(fen);
}
} else {
fen = AllocMem(sizeof(FENTRY), MEMF_PUBLIC);
if (fen->buf = AllocMem(left, MEMF_PUBLIC)) {
fen->bytes = left;
mfh->file->bytes += left;
mfh->base += left;
mfh->offset = 0;
TotalBytes += left;
AddTail(&mfh->file->list, fen);
/*DB*/ dbprintf("NEWFEN: (%ld)\n", fen->bytes);
bmov(ptr, fen->buf, left);
left = 0;
} else {
FreeMem(fen, sizeof(FENTRY));
/*DB*/ dbprintf("NEWFEN: ****** Unable to allocate buffer %ld\n", left);
mfh->offset = 0;
break;
}
fen = NULL; /* cause append */
}
}
PRes1 = PArg3 - left;
mfh->fentry = fen;
}
break;
case ACTION_CLOSE: /* FHArg1 Bool:TRUE */
{
register MYFH *mfh = (MYFH *)PArg1;
register RAMFILE *file = mfh->file;
Remove(mfh);
FreeMem(mfh,sizeof(*mfh));
if (--file->locks < 0)
file->locks = 0;
}
if (!GetHead(&FHBase) && !GetHead(&LCBase))
notdone = 0;
break;
case ACTION_SEEK: /* FHArg1,Position,Mode OldPosition*/
{
register MYFH *mfh = (MYFH *)PArg1;
register FENTRY *fen;
register long absseek;
PRes1 = mfh->base + mfh->offset;
absseek = PArg2;
if (PArg3 == 0)
absseek += mfh->base + mfh->offset;
if (PArg3 == 1)
absseek = mfh->file->bytes + absseek;
if (absseek < 0 || absseek > mfh->file->bytes) {
error = ERROR_SEEK_ERROR;
break;
}
mfh->base = mfh->offset = 0;
/*
* Stupid way to do it but....
*/
for (fen = GetHead(&mfh->file->list); fen; fen = NextNode(fen)) {
if (mfh->base + fen->bytes > absseek) {
mfh->offset = absseek - mfh->base;
break;
}
mfh->base += fen->bytes;
}
mfh->fentry = fen;
}
break;
/*
* This implementation sucks. The right way to do it is with
* a hash table. The directory must be searched for the file
* name, then the next entry retrieved. If the next entry is
* NULL there are no more entries. If the filename could not
* be found we return the first entry, if any.
*
* You can't simply keep a pointer around to the next node
* because it can be moved or removed at any time.
*/
case ACTION_EXAMINE_NEXT: /* Lock,Fib Bool */
{
register FIB *fib = BTOC(PArg2);
register RAMFILE *dir = getlockfile(PArg1);
register RAMFILE *file;
if (dir->type == FILE_FILE) {
error = ERROR_OBJECT_WRONG_TYPE;
break;
}
file = GetHead(&dir->list);
if (fib->fib_DiskKey) {
register int len = *(ubyte *)fib->fib_FileName;
for (; file; file = NextNode(file)) {
if (strlen(file->name) == len && nccmp(file->name, fib->fib_FileName+1, len))
break;
}
if (file)
file = NextNode(file);
else
file = GetHead(&dir->list);
}
fib->fib_DiskKey = 1;
error = -1;
if (!(tmp=file)) {
error = ERROR_NO_MORE_ENTRIES;
break;
}
}
/* fall through */
case ACTION_EXAMINE_OBJECT: /* Lock,Fib Bool */
{
register FIB *fib;
register RAMFILE *file;
register RAMFILE *dummy;
fib = BTOC(PArg2);
if (error) {
file = tmp; /* fall through from above */
} else {
file = getlockfile(PArg1);
fib->fib_DiskKey = 0;
}
error = 0;
fib->fib_DirEntryType = file->type;
strcpy(fib->fib_FileName+1, file->name);
fib->fib_FileName[0] = strlen(file->name);
fib->fib_Protection = file->protection;
fib->fib_EntryType = NULL;
fib->fib_Size = file->bytes;
fib->fib_NumBlocks = file->bytes >> 9;
fib->fib_Date = file->date;
if (file->comment) {
strcpy(fib->fib_Comment+1, file->comment);
fib->fib_Comment[0] = strlen(file->comment);
} else {
fib->fib_Comment[0] = 0;
}
}
break;
case ACTION_INFO: /* Lock, InfoData Bool:TRUE */
tmp = BTOC(PArg2);
error = -1;
/* fall through */
case ACTION_DISK_INFO: /* InfoData Bool:TRUE */
{
register INFODATA *id;
/*
* Note: id_NumBlocks is never 0, but only to get
* around a bug I found in my shell (where I divide
* by id_NumBlocks). Other programs probably break
* as well.
*/
(error) ? (id = tmp) : (id = BTOC(PArg1));
error = 0;
bzero(id, sizeof(*id));
id->id_DiskState = ID_VALIDATED;
id->id_NumBlocks = (TotalBytes >> 9) + 1;
id->id_NumBlocksUsed = (TotalBytes >> 9) + 1;
id->id_BytesPerBlock = 512;
id->id_DiskType = ID_DOS_DISK;
id->id_VolumeNode = (long)CTOB(DosNode);
id->id_InUse = (long)GetHead(&LCBase);
}
break;
case ACTION_PARENT: /* Lock ParentLock */
{
register RAMFILE *file = getlockfile(PArg1);
/*
if (file->type == FILE_FILE) {
error = ERROR_OBJECT_NOT_FOUND;
break;
}
*/
if (file->locks < 0) {
error = ERROR_OBJECT_IN_USE;
break;
}
if (file->parent)
PRes1 = (long)CTOB(ramlock(file->parent, ACCESS_READ));
else
error = ERROR_OBJECT_NOT_FOUND;
}
break;
case ACTION_DELETE_OBJECT: /*Lock,Name Bool */
{
RAMFILE *parentdir = getlockfile(PArg1);
RAMFILE *ramfile;
btos(PArg2, buf);
if (ramfile = searchpath(&parentdir,buf,NULL)) {
if (ramfile == &xpath) {
if (!DeleteFile(buf1)) error = IoErr();
break;
}
if (ramfile->locks || ramfile == &RFRoot) {
error = ERROR_OBJECT_IN_USE;
break;
}
if (ramfile->type == FILE_DIR) {
if (GetHead(&ramfile->list))
error = ERROR_DIRECTORY_NOT_EMPTY;
} else {
freedata(ramfile);
}
if (!error) {
freeramfile(ramfile);
DateStamp(&parentdir->date);
}
} else {
if (!parentdir)
error = ERROR_INVALID_COMPONENT_NAME;
else
error = ERROR_OBJECT_NOT_FOUND;
}
}
if (!GetHead(&FHBase) && !GetHead(&LCBase))
notdone = 0;
break;
case ACTION_CREATE_DIR: /* Lock,Name Lock */
{
RAMFILE *parentdir = getlockfile(PArg1);
RAMFILE *ramfile;
char *ptr;
btos(PArg2, buf);
if (ramfile = vsearchpath(&parentdir,buf,&ptr)) {
error = ERROR_OBJECT_EXISTS;
break;
}
if (!parentdir) {
error = ERROR_INVALID_COMPONENT_NAME;
break;
}
ramfile = createramfile(parentdir, FILE_DIR, ptr);
PRes1 = (long)CTOB(ramlock(ramfile, ACCESS_WRITE));
}
break;
case ACTION_LOCATE_OBJECT: /* Lock,Name,Mode Lock */
{
RAMFILE *parentdir = getlockfile(PArg1);
RAMFILE *ramfile;
btos(PArg2, buf);
/*DB*/ dbprintf("'%s' %ld ", buf, PArg3);
if (ramfile = searchpath(&parentdir,buf,NULL)) {
if (ramfile == &xpath) {
PRes1 = Lock(buf1,PArg3);
if (!PRes1) PRes2 = IoErr();
break;
}
if (ramfile->locks < 0 || (ramfile->locks && PArg3 == ACCESS_WRITE)) {
error = ERROR_OBJECT_IN_USE;
break;
}
PRes1 = (long)CTOB(ramlock(ramfile, PArg3));
} else {
if (!parentdir)
error = ERROR_INVALID_COMPONENT_NAME;
else
error = ERROR_OBJECT_NOT_FOUND;
}
}
break;
case ACTION_COPY_DIR: /* Lock, Lock */
{
register RAMFILE *ramfile = getlockfile(PArg1);
if (ramfile->locks < 0)
error = ERROR_OBJECT_IN_USE;
else
PRes1 = (long)CTOB(ramlock(ramfile, ACCESS_READ));
}
break;
case ACTION_FREE_LOCK: /* Lock, Bool */
if (PArg1);
ramunlock(BTOC(PArg1));
if (!GetHead(&FHBase) && !GetHead(&LCBase))
notdone = 0;
break;
case ACTION_SET_PROTECT:/* -,Lock,Name,Mask Bool */
{
register RAMFILE *ramfile;
RAMFILE *parentdir = getlockfile(PArg2);
char *ptr;
btos(PArg3, buf);
if (ramfile = searchpath(&parentdir,buf,&ptr)) {
if (ramfile == &xpath) {
if (!SetProtection(buf1,PArg4))
error = IoErr();
break;
}
ramfile->protection = PArg4;
} else {
if (parentdir)
error = ERROR_OBJECT_NOT_FOUND;
else
error = ERROR_INVALID_COMPONENT_NAME;
}
}
break;
case ACTION_SET_COMMENT:/* -,Lock,Name,Comment Bool */
{
register RAMFILE *ramfile;
RAMFILE *parentdir = getlockfile(PArg2);
char *ptr;
btos(PArg3, buf);
if (ramfile = searchpath(&parentdir,buf,&ptr)) {
if (ramfile == &xpath) {
btos(PArg4,buf);
if (!SetComment(buf1,buf))
error = IoErr();
break;
}
btos(PArg4, buf);
if (ramfile->comment)
FreeMem(ramfile->comment,strlen(ramfile->comment)+1);
ramfile->comment = AllocMem(strlen(buf)+1, MEMF_PUBLIC);
strcpy(ramfile->comment, buf);
} else {
if (parentdir)
error = ERROR_OBJECT_NOT_FOUND;
else
error = ERROR_INVALID_COMPONENT_NAME;
}
}
break;
case ACTION_RENAME_OBJECT:/* SLock,SName,DLock,DName Bool */
{
register RAMFILE *file1;
RAMFILE *sourcedir = getlockfile(PArg1);
RAMFILE *destdir = getlockfile(PArg3);
char *ptr;
btos(PArg2,buf);
/*DB*/ dbprintf("\nRENAME '%s' (%ld) ", buf, strlen(buf));
if (file1 = vsearchpath(&sourcedir,buf,NULL)) {
btos(PArg4,buf);
/*DB*/ dbprintf("TO '%s' (%ld)", buf, strlen(buf));
if (vsearchpath(&destdir,buf,&ptr)) {
error = ERROR_OBJECT_EXISTS;
} else {
if (destdir) {
if (file1 == destdir) {
/* moving inside self */
error = ERROR_OBJECT_IN_USE;
break;
}
/*DB*/ dbprintf("REN '%s' %ld", ptr, strlen(ptr));
DateStamp(&sourcedir->date);
DateStamp(&destdir->date);
Remove(file1);
file1->name = AllocMem(strlen(ptr)+1,MEMF_PUBLIC);
file1->parent = destdir;
strcpy(file1->name, ptr);
AddHead(&destdir->list, file1);
} else {
error = ERROR_INVALID_COMPONENT_NAME;
}
}
} else {
if (sourcedir)
error = ERROR_OBJECT_NOT_FOUND;
else
error = ERROR_INVALID_COMPONENT_NAME;
}
}
break;
/*
* A few other packet types which we do not support
*/
case ACTION_INHIBIT: /* Bool Bool */
/* Return success for the hell of it */
break;
case ACTION_RENAME_DISK:/* BSTR:NewName Bool */
case ACTION_MORECACHE: /* #BufsToAdd Bool */
case ACTION_WAIT_CHAR: /* Timeout, ticks Bool */
case ACTION_FLUSH: /* writeout bufs, disk motor off */
case ACTION_RAWMODE: /* Bool(-1:RAW 0:CON) OldState */
default:
error = ERROR_ACTION_NOT_KNOWN;
break;
}
if (packet) {
if (error) {
/*DB*/ dbprintf("ERR=%ld\n", error);
PRes1 = DOS_FALSE;
PRes2 = error;
} else {
/*DB*/ dbprintf("RES=%06lx\n", PRes1);
}
returnpacket(packet);
}
else {
/*DB*/ dbprintf("NOREP\n");
}
}
}
/*DB*/ dbprintf("Can we remove ourselves? ");
/*DB*/ Delay(50); /* I wanna even see the debug message! */
Forbid();
if (packetsqueued() || GetHead(&FHBase) || GetHead(&LCBase)
|| GetHead(&RFRoot.list)) {
Permit();
/*DB*/ dbprintf(" .. not yet!\n");
goto top; /* sorry... can't exit */
}
/*
* Causes a new process to be created on next reference
*/
DosNode->dn_Task = FALSE;
/*
* Remove Volume entry. Since DOS uses singly linked lists, we
* must (ugg) search it manually to find the link before our
* Volume entry.
*/
{
DOSINFO *di = BTOC(((ROOTNODE *)DOSBase->dl_Root)->rn_Info);
register DEVLIST *dl;
register void *dlp;
dlp = &di->di_DevInfo;
for (dl = BTOC(di->di_DevInfo); dl && dl != DevList; dl = BTOC(dl->dl_Next))
dlp = &dl->dl_Next;
if (dl == DevList) {
*(BPTR *)dlp = dl->dl_Next;
dosfree(dl);
} else {
/*DB*/ dbprintf("****PANIC: Unable to find volume node\n");
}
}
/*
* Remove debug process, closedown, fall of the end of the world
* (which is how you kill yourself if a PROCESS. A TASK would have
* had to RemTask(NULL) itself).
*/
/*DB*/ dbuninit();
CloseLibrary(DOSBase);
}
/*DB*/ static FH *debugfh;
/*DB*/
/*DB*/ dbinit()
/*DB*/ {
/*DB*/ debugfh = Open("con:0/0/640/150/debugwindow", 1006);
/*DB*/
/*DB*/ }
/*DB*/
/*DB*/ dbuninit()
/*DB*/ {
/*DB*/ Close(debugfh);
/*DB*/ }
/*DB*/
/*DB*/ dbprintf(a,b,c,d,e,f,g,h,i,j)
/*DB*/ {
/*DB*/ static char buf[256];
/*DB*/
/*DB*/ sprintf(buf,a,b,c,d,e,f,g,h,i,j);
/*DB*/ Write(debugfh,buf,strlen(buf));
/*DB*/
/*DB*/ }